from abc import ABC, abstractmethod
from typing import BinaryIO, Optional, Union, List, Dict
from pathlib import Path
import uuid
from datetime import datetime
import mimetypes
import hashlib
from fastapi import UploadFile


class BaseStorage(ABC):
    """存储策略基类"""

    def __init__(
        self,
        access_key: str = None,
        secret_key: str = None,
        endpoint: str = None,
        bucket: str = None,
        region: str = None,
        base_url: str = None,
    ):
        self.access_key = access_key
        self.secret_key = secret_key
        self.endpoint = endpoint
        self.bucket = bucket
        self.region = region
        self.base_url = base_url.rstrip("/") if base_url else None
        self.chunk_size = 5 * 1024 * 1024  # 5MB 分片大小

    @abstractmethod
    async def save(self, file: Union[BinaryIO, UploadFile], filename: str) -> str:
        """
        保存文件

        Args:
            file: 文件对象
            filename: 原始文件名

        Returns:
            str: 存储路径
        """
        pass

    @abstractmethod
    async def delete(self, file_path: str) -> bool:
        """
        删除文件

        Args:
            file_path: 存储路径

        Returns:
            bool: 是否删除成功
        """
        pass

    @abstractmethod
    async def get(self, file_path: str) -> Optional[BinaryIO]:
        """
        获取文件

        Args:
            file_path: 存储路径

        Returns:
            Optional[BinaryIO]: 文件对象
        """
        pass

    @abstractmethod
    async def get_url(self, file_path: str, expires_in: int = 3600) -> str:
        """
        获取文件访问URL

        Args:
            file_path: 存储路径
            expires_in: 过期时间(秒)

        Returns:
            str: 文件访问URL
        """
        pass

    @abstractmethod
    async def init_multipart_upload(self, filename: str, content_type: str) -> str:
        """
        初始化分片上传

        Args:
            filename: 文件名
            content_type: 文件类型

        Returns:
            str: 上传ID
        """
        pass

    @abstractmethod
    async def upload_part(self, upload_id: str, part_number: int, data: bytes) -> str:
        """
        上传分片

        Args:
            upload_id: 上传ID
            part_number: 分片序号
            data: 分片数据

        Returns:
            str: 分片ETag
        """
        pass

    @abstractmethod
    async def complete_multipart_upload(self, upload_id: str, parts: List[Dict[str, Union[int, str]]]) -> str:
        """
        完成分片上传

        Args:
            upload_id: 上传ID
            parts: 分片信息列表 [{'PartNumber': 1, 'ETag': 'xxx'}, ...]

        Returns:
            str: 存储路径
        """
        pass

    @abstractmethod
    async def abort_multipart_upload(self, upload_id: str) -> bool:
        """
        取消分片上传

        Args:
            upload_id: 上传ID

        Returns:
            bool: 是否成功
        """
        pass

    def generate_storage_path(self, filename: str) -> str:
        """
        生成存储路径

        Args:
            filename: 原始文件名

        Returns:
            str: 存储路径
        """
        # 生成UUID作为文件名
        file_uuid = str(uuid.uuid4())
        # 按日期分目录
        date_path = datetime.now().strftime("%Y/%m/%d")
        # 保留原始文件扩展名
        ext = self.get_file_extension(filename)

        return f"{date_path}/{file_uuid}{ext}"

    @staticmethod
    def get_file_extension(filename: str) -> str:
        """
        获取文件扩展名

        Args:
            filename: 文件名

        Returns:
            str: 文件扩展名
        """
        return Path(filename).suffix.lower()

    @staticmethod
    def get_content_type(filename: str) -> str:
        """
        获取Content-Type

        Args:
            filename: 文件名

        Returns:
            str: Content-Type
        """
        content_type, _ = mimetypes.guess_type(filename)
        return content_type or "application/octet-stream"

    @staticmethod
    async def calculate_md5(file: Union[BinaryIO, UploadFile]) -> str:
        """
        计算文件MD5（分块计算，避免大文件占用过多内存）

        Args:
            file: 文件对象

        Returns:
            str: MD5值
        """
        md5_hash = hashlib.md5()
        chunk_size = 8192  # 8KB 的块大小

        if isinstance(file, UploadFile):
            while chunk := await file.read(chunk_size):
                md5_hash.update(chunk)
            await file.seek(0)
        else:
            while chunk := file.read(chunk_size):
                md5_hash.update(chunk)
            file.seek(0)

        return md5_hash.hexdigest()
